package com.flow.framework.persistence.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.tenant.TenantSqlParser;
import com.flow.framework.common.error.SystemErrorCode;
import com.flow.framework.common.exception.CheckedException;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.core.properties.FrameworkCoreConfigProperties;
import com.flow.framework.core.toolkit.IdentifierGeneratorSequence;
import com.flow.framework.persistence.handler.CustomizationSecurityHandler;
import com.flow.framework.persistence.handler.CustomizationTenantHandler;
import com.flow.framework.persistence.manager.LocalDataSourceTransactionManager;
import com.flow.framework.persistence.parser.SystemVersionSqlParser;
import com.flow.framework.persistence.properties.FrameworkPersistenceConfigProperties;
import com.flow.framework.persistence.service.system.health.impl.DataSourceHealthCheckServiceImpl;
import com.flow.framework.persistence.system.listener.lifecycle.PersistenceHolderLifecycleListener;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.boot.autoconfigure.properties.SeataProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.ReflectionUtils;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.util.Arrays;

/**
 * 框架持久化配置
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/4/4
 */
@Slf4j
@Configuration
public class FrameworkPersistenceConfig {

    @Bean
    @ConditionalOnMissingBean
    @RefreshScope
    @ConfigurationProperties(prefix = "customization.framework.persistence")
    FrameworkPersistenceConfigProperties frameworkPersistenceConfigProperties() {
        return new FrameworkPersistenceConfigProperties();
    }

    /**
     * 默认不开启分布式事务
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    SeataProperties seataProperties() {
        SeataProperties seataProperties = new SeataProperties();
        seataProperties.setEnabled(false);
        return seataProperties;
    }

    @Bean
    @ConditionalOnMissingBean
    DataSource dataSource(DataSourceProperties dataSourceProperties,
                          FrameworkPersistenceConfigProperties frameworkPersistenceConfigProperties,
                          @Autowired(required = false) SeataProperties seataProperties) {
        Field secretField = VerifyUtil.requireNotNull(ReflectionUtils.findField(CustomizationSecurityHandler.class, "secret"),
                () -> {
                    log.error("can't find field. field name : secret");
                    return new CheckedException(SystemErrorCode.SYSTEM_START_ERROR);
                });
        secretField.setAccessible(true);
        ReflectionUtils.setField(secretField, null, frameworkPersistenceConfigProperties.getSecret());
        secretField.setAccessible(false);

        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setName(dataSourceProperties.getName());
        druidDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
        druidDataSource.setUrl(dataSourceProperties.getUrl());
        druidDataSource.setUsername(dataSourceProperties.getUsername());
        druidDataSource.setPassword(dataSourceProperties.getPassword());
        if (null == seataProperties || !seataProperties.isEnabled()) {
            return druidDataSource;
        }
        return new DataSourceProxy(druidDataSource);
    }

    /**
     * 优先使用本地事务，即：为了兼容未开启分布式事务场景，优先使用本地事务
     *
     * @param dataSource dataSource
     * @return
     */
    @Bean
    @Primary
    @ConditionalOnMissingBean
    LocalDataSourceTransactionManager localDataSourceTransactionManager(DataSource dataSource) {
        return new LocalDataSourceTransactionManager(dataSource);
    }

    @Bean
    @ConditionalOnMissingBean
    PaginationInterceptor paginationInterceptor(FrameworkPersistenceConfigProperties frameworkPersistenceConfigProperties,
                                                @Autowired(required = false) FrameworkCoreConfigProperties frameworkCoreConfigProperties) {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        TenantSqlParser tenantSqlParser = new TenantSqlParser()
                .setTenantHandler(new CustomizationTenantHandler(frameworkPersistenceConfigProperties, frameworkCoreConfigProperties));
        SystemVersionSqlParser systemVersionSqlParser = new SystemVersionSqlParser(frameworkPersistenceConfigProperties,
                frameworkCoreConfigProperties);
        paginationInterceptor.setSqlParserList(Arrays.asList(tenantSqlParser, systemVersionSqlParser));
        return paginationInterceptor;
    }

    @Bean
    @ConditionalOnMissingBean
    IdentifierGenerator identifierGenerator(IdentifierGeneratorSequence identifierGeneratorSequence) {
        IdentifierGenerator identifierGenerator = entity -> identifierGeneratorSequence.nextId();
        IdWorker.setIdentifierGenerator(identifierGenerator);
        return identifierGenerator;
    }

    @Bean
    @ConditionalOnMissingBean
    DataSourceHealthCheckServiceImpl dataSourceHealthCheckService(DataSource dataSource) {
        return new DataSourceHealthCheckServiceImpl(dataSource);
    }

    @Bean
    @ConditionalOnMissingBean
    PersistenceHolderLifecycleListener persistenceHolderLifecycleListener(LocalDataSourceTransactionManager localTransactionManager) {
        return new PersistenceHolderLifecycleListener(localTransactionManager);
    }
}
